// console.log('a')
// let i = 0
// if (i < 3) {let i = 'foo'; console.log(i)} i++
// if (i < 3) {let i = 'foo'; console.log(i)} i++
// if (i < 3) {let i = 'foo'; console.log(i)} i++

/**
 * 数组解构
 */
// const arr = [100, 200, 300]
// const [, , baz] = arr
// console.log(baz) // 300
// const [foo, , , more = '1'] = arr
// console.log(more) // 200 300

/**
 * 对象解构
 */
// const obj = { name: 'foo', age: 22 }
// const { name: {objName = '1'} } = obj
// console.log(objName) // 1
// const { log } = console
// log('a') // a

/**
 * 模板字符串
 */
// const str = 'hello es2015, \nthis is a string'
// console.log(str)
// const name = 'es2015'
// const str2 = `hello ${name},
// this is a string`
// console.log(str2)

// const str = console.log`hello world` // [ 'hello world' ]


/**
 * 模板字符串的方法，也可以接收到模板字符串中的变量
 * @param {array} strings 模板字符串分割后的结果：因为模板字符串中有嵌入的表达式
 * @param {*} name 模板字符串中的变量
 * @param {*} gender 模板字符串中的变量
 * @return // 带标签的模板字符串所对应的返回值
 */
// function myTagFunc(strings, name, gender) {
//   console.log(strings) // [ 'hey, ', ' is a ', '' ]
//   console.log(name, gender) // tom true
//   const sex = gender ? 'man' : 'woman'
//   return strings[0] + name + strings[1] + sex + strings[2]
// }
// const name = 'tom'
// const gender = true
// const result = myTagFunc`hey, ${name} is a ${gender}` // 定义使用标签函数的模板字符串
// console.log(result) // hey, tom is a man

/**
 * 对比：
 * 默认值只有在没有传入实参 or 实参是 undefined时会使用
 *【注意】：多个值，有默认值的参数要写在最后
 */
// function foo (flag, enable = true) {
//   // 大多数会写成这样，但是会导致 enable的值为false时，也使用了默认值
//   // enable = enable || true
//   // 改写为判断传入值是否为undefined
//   console.log(enable)
// }
// foo('1')

// function foo() {
//   console.log(arguments) // arguments 是伪数组
// }
// foo(1, 2, 3, 4)

// function foo(...args) {
//   console.log(args)
// }
// foo(1, 2, 3, 4) // [ 1, 2, 3, 4 ]

// const arr = ['foo', 'bar', 'baz']
// console.log(arr[0], arr[1], arr[2])

// 对比
// console.log.apply(console, arr)
// console.log(...arr)

// 箭头函数
// const person = {
//   name: 'tom',
//   sayHi: function () {
//     console.log(`hi, my name is ${this.name}`) // hi, my name is tom
//   },
//   sayHello: () => {
//     console.log(`hello, my name is ${this.name}`) // hello, my name is undefined
//   },
//   sayHiAsync: function () {
//     /**
//      * setTimout 中是个普通函数，无法拿到 this，拿到的是全局 this
//      * 因为这个函数在 setTimout 内部最终会放到全局对象上被调用
//      * 因此需要定义一个变量 _this，保存当前作用域的 this
//      * 借助闭包的机制，在内部使用 _this
//      */
//     const _this = this
//     setTimeout(function () {
//       console.log(this.name, 'sayhi') // undefined
//       console.log(_this.name, 'sayhi') // tom
//     }, 1000);
//   },
//   sayHelloAsync: function () {
//     setTimeout(() =>  {
//       console.log(this.name, 'sayhello') // tom
//     }, 1000);
//   }
// }
// person.sayHi()
// person.sayHello()
// person.sayHiAsync()
// person.sayHelloAsync()

// const bar = 'bar'
// const prop = 'prop'
// const obj = {
//   foo: 123,
//   bar, // 等价于 bar: bar
//   method() {
//     console.log('method')
//     console.log(this) // 内部的 this 指向当前对象
//   },
//   [prop]: 456
// }
// console.log(obj)
// obj.method()

// const source1 = { a: 123, b: 456 }
// const target = { a: 789, c: 'ccc' }
// const result = Object.assign(target, source1)
// console.log(target) // { a: 123, c: 'ccc', b: 456 }
// console.log(result === target) // true

// function func(obj) {
//   obj.name = 'func obj'
//   console.log(obj) // { name: 'func obj' }
// }
// const obj = { name: 'global obj' }
// func(obj)
// console.log(obj) // { name: 'func obj' }

// function func2(obj) {
//   const funcObj = Object.assign({}, obj)
//   funcObj.name = 'func obj'
//   console.log(funcObj) // { name: 'func obj' }
// }
// const obj2 = { name: 'global obj' }
// func2(obj2)
// console.log(obj2) // { name: 'global obj' }


// console.log(0 == false, 0 === false, +0 === -0, NaN === NaN)

// // 对比
// console.log(Object.is(0, false), Object.is(+0, -0), Object.is(NaN, NaN))

/**
 * Reflect
 */
// const person = {
//   name: 'wangwu',
//   age: 11
// }
// const proxy = new Proxy(person, {
//   get(target, property) {
//     console.log('watch logic-')
//     return Reflect.get(target, property)
//   }
// })
// console.log(proxy.name) // wangwu

// const obj = {
//   foo: 'foo',
//   bar: 'bar'
// }
// console.log(Reflect.has(obj, 'name'))
// console.log(Reflect.deleteProperty(obj, 'foo'))
// console.log(Reflect.ownKeys(obj))

/**
 * Set
 */
// // 由于它是个类型，因此使用类型构造的实例可存放不重复的数据
// const a = new Set()

// // 可调用它的 add 方法，由于此方法返回集合对象本身，因此可以链式调用，若添加了之前存在的值，则会被忽略
// a.add(1).add(2).add(2).add(3).add(5)
// console.log(a) // Set { 1, 2, 3, 5 }

// // 可使用 forEach 、 for...of、遍历集合
// a.forEach(i => console.log(i))
// for(let i of a) { console.log(i) }

// // 可使用 size 获取集合长度
// console.log(a.size) // 4

// // 可使用 has 获取集合是否包含某一个值
// console.log(a.has(100)) // false

// // delete 删除某一个值，成功后返回 true
// console.log(a.delete(3)) // true

// // 清除集合中的全部内容
// a.clear()
// console.log(a) // Set {}

// // 去重
// const arr = [1, 3, 2, 4, 2, 1, 3]
// const result = new Set(arr)
// console.log(result) // Set { 1, 3, 2, 4 }
// console.log(Array.from(result)) // [ 1, 3, 2, 4 ]

// const result2 = [...new Set(arr)]
// console.log(result2)


/**
 * Map
 */
// const obj = {}
// obj[123] = 'value'
// obj[true] = 'value'
// obj[{ a: 1 }] = 'value'
// console.log(Object.keys(obj)) // [ '123', 'true', '[object Object]' ]

// 对比
// const m = new Map()
// const tom = { name: 'tom' }

// // 设置键值
// m.set(tom, 90)
// m.set(123, '123')
// console.log(m) // Map(2) { { name: 'tom' } => 90, 123 => '123' }

// // 获取值
// console.log(m.get(tom)) // 90

// // 判断map中是否有某个属性
// console.log(m.has(tom)) // true

// // 遍历map
// m.forEach((value, key) => {
//   console.log(key, value) // { name: 'tom' } 90
// })

// // 删除某个属性
// console.log(m.delete(123)) // true

// // 清除map
// m.clear()
// console.log(m) // Map(0) {}


/**
 * for...of
 */
// const arr = [100, 200, 300, 400]
// for(const item of arr) {
//   if (item > 200) {
//     break
//   }
//   console.log(item)
// }

// const obj = { foo: 123, bar: 456 }
// for(const item of obj) {
//   console.log(item) // obj is not iterable。说明 obj 不可被迭代
// }

/**
 * es2017
 */
const obj = {
  foo: 'value1',
  bar: 'value2'
}
console.log(Object.entries(obj)) // [ [ 'foo', 'value1' ], [ 'bar', 'value2' ] ]

for (const [key, value] of Object.entries(obj)) {
  console.log(key, value) // foo value1; bar value2
}

console.log(new Map(Object.entries(obj))) // Map(2) { 'foo' => 'value1', 'bar' => 'value2' }

// Object.getOwnPropertyDescriptors
// const p1 = {
//   firstName: 'li',
//   lastName: 'lei',
//   get fullName () {
//     return this.firstName + ' ' + this.lastName
//   }
// }
// const p2 = Object.assign({}, p1)
// p2.firstName = 'zhang'
// // 将 p1 中的fullName 当做普通属性复制了
// console.log(p2) // { firstName: 'zhang', lastName: 'lei', fullName: 'li lei' }

// const descripors = Object.getOwnPropertyDescriptors(p1)
// const p3 = Object.defineProperties({}, descripors)
// p3.firstName = 'wang'
// console.log(p2)

const books = {
  html: 4,
  css: 10,
  js: 120
}
for (const [name, count] of Object.entries(books)) {
  console.log(`${name.padEnd(16, '-')} | ${count.toString(). padStart(3, '0')}`)
}
// html------------ | 004
// css------------- | 010
// js-------------- | 120